/*
 * \brief   Pistachio platform interface implementation
 * \author  Christian Helmuth
 * \date    2006-04-11
 */

/*
 * Copyright (C) 2006-2009 Christian Helmuth
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

/* Genode */
#include <base/printf.h>
#include <base/allocator_avl.h>
#include <base/crt0.h>
#include <base/sleep.h>
#include <util/misc_math.h>

/* Core */
#include <platform.h>
#include <platform_thread.h>
#include <platform_pd.h>
#include <util.h>
#include <pistachio/thread_helper.h>
#include <pistachio/kip.h>

namespace Pistachio {
#include <l4/kip.h>
#include <l4/sigma0.h>
#include <l4/space.h>
#include <l4/bootinfo.h>
#include <l4/schedule.h>
}

using namespace Genode;


/* DEBUGGING */
static bool verbose  = false;
static bool verbose2 = false;


/***********************************
 ** Core address space management **
 ***********************************/

static Synchronized_range_allocator<Allocator_avl> &_core_address_ranges()
{
	static Synchronized_range_allocator<Allocator_avl> _core_address_ranges(0);
	return _core_address_ranges;
}

enum { PAGER_STACK_ELEMENTS = 512 };
static unsigned long _core_pager_stack[PAGER_STACK_ELEMENTS];


static inline bool is_write_fault(Pistachio::L4_Word_t flags) {
	return (flags & 2) == 1; }


static bool wait_for_page_fault(Pistachio::L4_ThreadId_t &from,
                                Pistachio::L4_Word_t     &pf_addr,
                                Pistachio::L4_Word_t     &pf_ip,
                                Pistachio::L4_Word_t     &flags)
{
	using namespace Pistachio;

	L4_Accept(L4_UntypedWordsAcceptor);
	L4_MsgTag_t res = L4_Wait(&from);
	L4_Msg_t msg;

	if (L4_IpcFailed(res) || (L4_UntypedWords(res)) != 2) {
		printf("page fault: IPC ERROR");
		return false;
	}
	L4_Store(res, &msg);

	pf_addr = L4_Get(&msg, 0);
	pf_ip   = L4_Get(&msg, 1);
	flags   = res.X.flags;
	return true;
}


static bool reply_and_wait_for_page_fault(Pistachio::L4_ThreadId_t  to,
                                          Pistachio::L4_MapItem_t   item,
                                          Pistachio::L4_ThreadId_t &from,
                                          Pistachio::L4_Word_t     &pf_addr,
                                          Pistachio::L4_Word_t     &pf_ip,
                                          Pistachio::L4_Word_t     &flags)
{
	using namespace Pistachio;

	L4_Msg_t msg;
	L4_Clear(&msg);
	L4_Append(&msg, item);
	L4_Accept(L4_UntypedWordsAcceptor);
	L4_MsgLoad(&msg);

	L4_MsgTag_t res = L4_ReplyWait(to, &from);

	if (L4_IpcFailed(res) || (L4_UntypedWords(res)) != 2) {
		printf("page fault: IPC ERROR");
		return wait_for_page_fault(from, pf_addr, pf_ip, flags);
	}
	L4_Store(res, &msg);

	pf_addr = L4_Get(&msg, 0);
	pf_ip   = L4_Get(&msg, 1);
	flags   = res.X.flags;
	return true;
}


/**
 * Core pager "service loop"
 */
static void _core_pager_loop()
{
	if (verbose) PDBG("Core pager running.");

	using namespace Pistachio;

	L4_ThreadId_t t;
	L4_Word_t pf_addr, pf_ip;
	L4_Word_t page_size = get_page_size();
	L4_Word_t flags;

	while (1) {
		PDBG("Waiting for page fault.");
		wait_for_page_fault(t, pf_addr, pf_ip, flags);

		while (2) {
			PDBG("Got page fault (pf_addr = %08lx, pf_ip = %08lx, flags = %08lx).",
			     pf_addr, pf_ip, flags);
			print_l4_threadid(L4_GlobalId(t));
			/* ignore messages from non-core tasks */
			// XXX How? By inspecting our Platform_threads...
#warning "TODO Ignore fault messages from non-core tasks"

			/* check for NULL pointer */
			if (pf_addr < page_size) {
				PERR("possible null pointer %s at address %lx at EIP %lx in",
				     is_write_fault(flags) ? "WRITE" : "READ/EXEC", pf_addr, pf_ip);
				print_l4_threadid(t);
				/* do not unblock faulter */
				break;
			} else if (!_core_address_ranges().valid_addr(pf_addr)) {
				/* page-fault address is not in RAM */

				PERR("%s access outside of RAM at %lx IP %lx",
				     is_write_fault(flags) ? "WRITE" : "READ", pf_addr, pf_ip);
				print_l4_threadid(t);
				/* do not unblock faulter */
				break;
			} else if (verbose2) {
				PDBG("pfa=%lx ip=%lx in", pf_addr, pf_ip);
				print_l4_threadid(t);
			}

			/* my pf handler is sigma0 - just touch the appropriate page */
			L4_Fpage_t res = L4_Sigma0_GetPage(get_sigma0(),
			                                   L4_Fpage(trunc_page(pf_addr), page_size));
			if (L4_IsNilFpage(res)) {
				panic("Unhandled page fault");
			}

			/* unblock faulter and wait for next pagefault */
			L4_Fpage_t fpage = L4_Fpage(pf_addr, page_size);
			fpage += L4_FullyAccessible;

			PDBG("Replying and waiting for page fault.");
			reply_and_wait_for_page_fault(t, L4_MapItem(fpage, pf_addr),
			                              t, pf_addr, pf_ip, flags);
		}
	}
}


void Platform::_setup_core_pager()
{
	using namespace Pistachio;

	/* create core pager, which is paged by Sigma0 */
	_core_pager = new(&_ram_alloc) Platform_thread("core.pager0");
	_core_pager->pager_cap(Thread_capability(Capability(get_sigma0(), 0)));
	_core_pd->bind_thread(_core_pager);

	/* stack begins at the top end of the '_core_pager_stack' array */
	void *sp = (void *)&_core_pager_stack[PAGER_STACK_ELEMENTS - 1];
	_core_pager->start((void *)_core_pager_loop, sp);

	PDBG("Core pager has L4_ThreadId %08lx.", _core_pager->l4_thread_id().raw);

	/* pager0 receives pagefaults from me - for NULL pointer detection */
	L4_Set_Pager(_core_pager->l4_thread_id());

//	L4_ThreadSwitch(_core_pager->l4_thread_id());
}


/***********************************
 ** Helper for L4 region handling **
 ***********************************/

struct Region
{
	addr_t start;
	addr_t end;

	Region() : start(0), end(0) { }
	Region(addr_t s, addr_t e) : start(s), end(e) { }
};


/**
 * Log region
 */
static inline void print_region(Region r)
{
	printf("[%08x,%08x) %08x", r.start, r.end, r.end - r.start);
}


/**
 * Add region to allocator
 */
static inline void add_region(Region r, Range_allocator &alloc)
{
	if (r.start >= r.end) {
		PERR("(start = 0x%08lx, end = 0x%08lx)\n", r.start, r.end);
		Pistachio::panic("add_region called with bogus parameters.");
	}

	if (verbose && verbose2) {
		printf("%p    add: ", &alloc); print_region(r); printf("\n");
	}

	/* adjust region */
	addr_t start = Pistachio::round_page(r.start);
	addr_t end   = Pistachio::trunc_page(r.end);

	alloc.add_range(start, end - start);
}


/**
 * Remove region from allocator
 */
static inline void remove_region(Region r, Range_allocator &alloc)
{
	if (r.start >= r.end)
		Pistachio::panic("remove_region called with bogus parameters.");

	if (verbose && verbose2) {
		printf("%p remove: ", &alloc); print_region(r); printf("\n");
	}

	/* adjust region */
	addr_t start = Pistachio::trunc_page(r.start);
	addr_t end   = Pistachio::round_page(r.end);

	alloc.remove_range(start, end - start);
}


static void dump_kip_memdesc(Pistachio::L4_KernelInterfacePage_t *kip)
{
	using namespace Pistachio;

	L4_Word_t num_desc = L4_NumMemoryDescriptors(kip);
	static const char *types[16] =
	{
		"Undefined", "Conventional", "Reserved by kernel",
		"Dedicated", "Shared", "?", "?", "?", "?", "?", 
		"?", "?", "?", "?", "Boot loader",
		"Architecture-dependent"
	};

	for (L4_Word_t i = 0; i < num_desc; i++) {
		L4_MemoryDesc_t *d = L4_MemoryDesc(kip, i);

		printf("mem %d: [0x%08lx, 0x%08lx) type=0x%lx (%s) %s\n",
		       i,
		       L4_Low(d),
		       L4_High(d)+1,
		       L4_Type(d), types[L4_Type(d) & 0xF],
		       L4_IsVirtual(d) ? "Virtual" : "Non-Virtual");
	}
}


/**
 * Request any RAM page from Sigma0
 */
bool sigma0_req_region(addr_t *addr, unsigned log2size)
{
	using namespace Pistachio;

	L4_Fpage_t fpage = L4_Sigma0_GetAny(get_sigma0(), log2size,
	                                    L4_CompleteAddressSpace);

	if (L4_IsNilFpage(fpage))
		return false;

	*addr = L4_Address(fpage);
	return true;
}


void Platform::_setup_mem_alloc()
{
	Pistachio::L4_Word_t page_size_mask = Pistachio::L4_PageSizeMask(Pistachio::get_kip());
	unsigned int size_log2;

	/*
	 * Only support 4K pages because mappings of 4K pages out
	 * of 4M pages cause problems on Pistachio.
	 */
	page_size_mask &= 1 << 12;

	/*
	 * Allocate all memory from sigma0 in descending page sizes. Only
	 * try page sizes that are hardware supported.
	 */
	for ( size_log2 = 31; page_size_mask != 0; size_log2-- ) {

		unsigned int size = 1 << size_log2;

		/* if this page size is not supported try next */
		if ((page_size_mask & size) == 0)
			continue;

		/* mask out that size bit */
		page_size_mask &= ~size;

		printf("Trying to allocate %uK pages from sigma0.\n", size >> 10);

		/*
		 * Suck out sigma0. The spec says that we get only "conventional
		 * memory". Let's hope this is true.
		 */
		bool succ;
		unsigned int bytes_got = 0;
		do {
			addr_t addr;
			Region region;

			succ = sigma0_req_region(&addr, size_log2);
			if (succ) {
				/* XXX do not allocate page0 */
				if (addr == 0) {
//					L4_Fpage_t f = L4_FpageLog2(0, pslog2);
//					f += L4_FullyAccessible;
//					L4_Flush(f);

				} else {
					region.start = addr; region.end = addr + size;
					add_region(region, _ram_alloc);
					add_region(region, _core_address_ranges());
					remove_region(region, _io_mem_alloc);
					remove_region(region, _region_alloc);

					/* fill mapping with zeros */
					memset(reinterpret_cast<void *>(addr), 0, size);
				}

				bytes_got += size;
			}
		} while (succ);

		printf("Got %uK in %uK pieces.\n", bytes_got >> 10, size >> 10);
	}
}


void Platform::_setup_irq_alloc() {
  _irq_alloc.add_range(0, 0x10); }


void Platform::_setup_preemption()
{
	/*
	 * The roottask has the maximum priority (at least on Pistachio).
	 *
	 * XXX The following code is disabled. It sets the priority of us
	 * (the roottask) to the priority of all other threads. This
	 * priority happens to be 100 on Pistachio. This is required to use
	 * L4_Yield in the lock implementation instead of sleeping.
	 *
	 * L4_Set_Priority(L4_Myself(), 100);
	 */
}


void Platform::_setup_basics()
{
	using namespace Pistachio;

	/* completely map program image by touching all pages read-only */
	// XXX Why?
	PDBG("Let's check if we are complete...");
	volatile const char *beg, *end;
	beg = (const char *)(((unsigned)&_prog_img_beg) & ((1<<12)-1));
	end = (const char *)&_prog_img_end;
	for ( ; beg < end; beg += 4096) {
		// (void)(*beg);
		L4_Sigma0_GetPage(get_sigma0(),
		 L4_Fpage((L4_Word_t)beg, 4096));
	}
	PDBG("DONE.");

	/* store mapping base from received mapping */
	L4_KernelInterfacePage_t *kip = (L4_KernelInterfacePage_t *)get_kip();

	if (kip->magic != L4_MAGIC)
		Pistachio::panic("we got something but not the KIP");

	if (verbose) {
		printf("\n");
		printf("KIP @ %p\n", kip);
		printf("    magic: %08x\n", kip->magic);
		printf("  version: %08x\n", kip->ApiVersion.raw);
		printf(" BootInfo: %08x\n", kip->BootInfo);
	}

	dump_kip_memdesc(kip);

	/* add KIP as ROM module */
	_kip_rom = Rom_module((addr_t)kip, sizeof(L4_KernelInterfacePage_t), "pistachio_kip");
	_rom_fs.insert(&_kip_rom);

	/* update multi-boot info pointer from KIP */
	void *mb_info_ptr = (void *)kip->BootInfo;

	// Get virtual bootinfo address.

	PDBG("Requesting the BootInfo page from Sigma0.");
	L4_Fpage_t bipage = L4_Sigma0_GetPage(get_sigma0(),
	                                      L4_Fpage(kip->BootInfo,
	                                      get_page_size()));
	if (L4_IsNilFpage(bipage))
		panic("Could not map BootInfo.");

	if (!L4_BootInfo_Valid(mb_info_ptr))
		panic("No valid boot info.");

	if (L4_BootInfo_Size(mb_info_ptr) > get_page_size())
		panic("TODO Our multiboot info is bigger than a page...");
	PDBG("Multiboot size is %lu bytes.", L4_BootInfo_Size(mb_info_ptr));

	/* done magic */

	_mb_info = Multiboot_info(mb_info_ptr);
	if (verbose) printf("MBI @ %p\n", mb_info_ptr);

	/* get UTCB memory */
	Platform_pd::touch_utcb_space();

	/*
	 * Configure applicable address space
	 *
	 * FIXME use kernel info if supported (mind the 2G/2G split)
	 *
	 * KIP and UTCB are in 0xb000000. So do not use that.
	 */

#warning "XXX Use KIP!"
	_vm_start = 0x1000;
	_vm_size  = 0xb0000000 - 0x1000;
	_region_alloc.add_range(_vm_start, _vm_size);

	/*
	 * I/O memory could be the whole user address space
	 *
	 * FIXME if the kernel helps to find out max address - use info here
	 */
	_io_mem_alloc.add_range(0, ~0);

	/* remove KIP and MBI area from region and IO_MEM allocator */
	unsigned int kip_size = sizeof(L4_KernelInterfacePage_t);

	PDBG("KIP @ %p (size 0x%08x)", kip, kip_size);

	remove_region(Region((addr_t)kip, (addr_t)kip + kip_size), _region_alloc);
	remove_region(Region((addr_t)kip, (addr_t)kip + kip_size), _io_mem_alloc);
	remove_region(Region((addr_t)mb_info_ptr, (addr_t)mb_info_ptr + _mb_info.size()), _region_alloc);
	remove_region(Region((addr_t)mb_info_ptr, (addr_t)mb_info_ptr + _mb_info.size()), _io_mem_alloc);

	/* remove utcb area */
	addr_t utcb_ptr = (addr_t)Platform_pd::_core_utcb_ptr;

	PDBG("UTCB base @ %08lx (size %08lx)", utcb_ptr, L4_UtcbAreaSize (kip));

	remove_region(Region(utcb_ptr, utcb_ptr + L4_UtcbAreaSize (kip)), _region_alloc);
	remove_region(Region(utcb_ptr, utcb_ptr + L4_UtcbAreaSize (kip)), _io_mem_alloc);

	/* remove core program image memory from region allocator */
	addr_t img_start = (addr_t) &_prog_img_beg;
	addr_t img_end   = (addr_t) &_prog_img_end;
	remove_region(Region(img_start, img_end), _region_alloc);
	remove_region(Region(img_start, img_end), _io_mem_alloc);

	PDBG("_setup_basics done!");

	/* image is accessible by core */
	add_region(Region(img_start, img_end), _core_address_ranges());
}


void Platform::_setup_rom()
{
	Rom_module rom;

	Pistachio::L4_Word_t page_size = Pistachio::get_page_size();

	for (unsigned i = 0; i < _mb_info.num_modules();  i++) {
		if (!(rom = _mb_info.get_module(i)).valid()) continue;

		Rom_module *new_rom = new(ram_alloc()) Rom_module(rom);

		_rom_fs.insert(new_rom);

		if (verbose)
			printf(" mod[%d] [%08p,%08p) %s\n", i,
			       new_rom->addr(), ((char *)new_rom->addr()) + new_rom->size(),
			       new_rom->name());

		/* zero remainder of last ROM page */
		size_t count = page_size - rom.size() % page_size;
		if (count != page_size)
			memset(reinterpret_cast<void *>(rom.addr() + rom.size()), 0, count);

		/* remove ROM area from region and IO_MEM allocator */
		remove_region(Region(new_rom->addr(), new_rom->addr() + new_rom->size()), _region_alloc);
		remove_region(Region(new_rom->addr(), new_rom->addr() + new_rom->size()), _io_mem_alloc);

		/* add area to core-accessible ranges */
		add_region(Region(new_rom->addr(), new_rom->addr() + new_rom->size()), _core_address_ranges());
	}
}


Platform::Platform() :
	_io_mem_alloc(ram_alloc()), _io_port_alloc(ram_alloc()),
	_irq_alloc(ram_alloc()), _region_alloc(ram_alloc())
{
	/*
	 * We must be single-threaded at this stage and so this is safe.
	 */
	static bool initialized = 0;
	if (initialized) Pistachio::panic("Platform constructed twice!");
	initialized = true;

	PDBG("> setup_basics");
	_setup_basics();
	PDBG("> setup_preemption");
	_setup_preemption();
	PDBG("> setup_mem_alloc");
	_setup_mem_alloc();
	PDBG("> setup_port_alloc");
	_setup_io_port_alloc();
	PDBG("> setup_irq_alloc");
	_setup_irq_alloc();
	PDBG("> setup_rom");
	_setup_rom();

	/*
	 * When dumping 'ram_alloc', there are several small blocks in addition
	 * to the available free memory visible. These small blocks are used to
	 * hold the meta data for the ROM modules as initialized by '_setup_rom'.
	 */
	if (verbose) {
		printf(":ram_alloc: ");    _ram_alloc.raw()->dump_addr_tree();
		printf(":region_alloc: "); _region_alloc.raw()->dump_addr_tree();
		printf(":io_mem: ");       _io_mem_alloc.raw()->dump_addr_tree();
		printf(":io_port: ");      _io_port_alloc.raw()->dump_addr_tree();
		printf(":irq: ");          _irq_alloc.raw()->dump_addr_tree();
		printf(":rom_fs: ");       _rom_fs.print_fs();
		printf(":core ranges: ");  _core_address_ranges().raw()->dump_addr_tree();
	}

	/* setup task object for core task */
	_core_pd = new(ram_alloc()) Platform_pd(true);

	/*
	 * We setup the thread object for thread0 in core task using a
	 * special interface that allows us to specify the thread
	 * ID. For core this creates the situation that task_id ==
	 * thread_id of first task. But since we do not destroy this
	 * task, it should be no problem.
	 */
	Platform_thread *core_thread =
		new(ram_alloc()) Platform_thread("core.main");

	core_thread->set_l4_thread_id(Pistachio::L4_MyGlobalId());
	core_thread->pager_cap(Thread_capability(Capability(Pistachio::get_sigma0(), 0)));

	_core_pd->bind_thread(core_thread);

	PDBG("Trying to start core pager.");

	/* setup core pager */
	_setup_core_pager();
	PDBG("Done starting core pager. We are:");
	print_l4_threadid(Pistachio::L4_Myself());
}


/********************************
 ** Generic platform interface **
 ********************************/

void Platform::wait_for_exit()
{
	/*
	 * On Pistachio, core never exits. So let us sleep forever.
	 */
	sleep_forever();
}


/****************************
 ** Allocator dump helpers **
 ****************************/

/* FIXME candidates for generic utility */
void Allocator_avl_base::Block::dump()
{
	printf(" Block: [%08lx,%08x) size=%08lx avail=%08lx max_avail=%08lx\n",
	       addr(), addr() + size(), size(), avail(), max_avail());
}


/* FIXME candidates for generic utility */
void Allocator_avl_base::dump_addr_tree(Block *addr_node)
{
	bool top = false;
	static unsigned mem_size;
	static unsigned mem_avail;

	if (addr_node == 0) {
		addr_node = _addr_tree.first();

		printf("Allocator %p dump:\n", this);
		mem_size = mem_avail = 0;
		top = true;
	}

	if (!addr_node) return;

	if (addr_node->child(0))
		dump_addr_tree(addr_node->child(0));

	Block *b = (Block *)addr_node;
	b->dump();
	mem_size  += b->size();
	mem_avail += b->avail();

	if (addr_node->child(1))
		dump_addr_tree(addr_node->child(1));

	if (top)
		printf(" => mem_size=%u (%u MB) / mem_avail=%u (%u MB)\n",
		       mem_size, mem_size / 1024 / 1024,
		       mem_avail, mem_avail / 1024 / 1024);
}
